Skip to content

feat: add PUT /users/:id endpoint with strict field validation - #80

Open
MariannaWay wants to merge 3 commits into
mate-academy:mainfrom
MariannaWay:feat/put-users-id
Open

feat: add PUT /users/:id endpoint with strict field validation#80
MariannaWay wants to merge 3 commits into
mate-academy:mainfrom
MariannaWay:feat/put-users-id

Conversation

@MariannaWay

Copy link
Copy Markdown

What changed

Adds a PUT /users/:id endpoint for updating an existing user, following the patterns already established in routes/users.js and db/store.js.

db/store.js — new updateUser(id, { name, email }), written in the style of getUserById/createUser. It reuses getUserById to locate the record, updates name and email, and returns the updated user, or undefined when no user matches. Validation stays out of the store, consistent with how createUser works.

routes/users.js — new router.put("/:id", ...) handler:

  • parses id with Number(req.params.id), exactly as the existing GET /:id handler does
  • returns 400 { error: "name and email are required" } when either field is missing or invalid — same status and message shape as POST /
  • returns 404 { error: "User not found" } when the user doesn't exist — same shape as GET /:id
  • otherwise returns 200 with the updated user

Why

PUT /users/:id was the missing verb in the users resource — the API could list, fetch, and create users but not modify one. tests/update-user.test.js specifies the expected behavior; this makes it pass.

The three commits are deliberately separate so the history shows the reasoning:

Commit Purpose
375d24f The feature: store helper + route
c49994e A fix for a real gap found during self-review, kept apart from the feature commit
e07e857 NOTES.md (plan, model choice, review findings)

The review fix, in detail

The first pass validated with if (!name || !email), mirroring POST /. That's a truthiness check, not a type check, so these all returned 200 and were written to the store:

PUT /users/1  {"name": [],   "email": {}}    → 200  {"id":1,"name":[],"email":{}}
PUT /users/1  {"name": 123,  "email": 456}   → 200  {"id":1,"name":123,"email":456}
PUT /users/1  {"name": true, "email": true}  → 200  {"id":1,"name":true,"email":true}
PUT /users/1  {"name": "   ","email": "   "} → 200  (blank name persisted)

An empty array is truthy, so "no value" in array form read as present. This matters more on PUT than on POST: a bad POST adds one junk record, whereas a bad PUT silently destroys a valid user's data, and the in-memory store has no persistence or history to recover from.

c49994e adds an isNonEmptyString helper (typeof value === "string" && value.trim() !== "") and uses it in the PUT guard. All four cases above now return 400 with the unchanged error message.

Deliberately out of scope

Noting these so reviewers know they were considered rather than missed:

  • Id coercionNumber(req.params.id) accepts /users/1e0, /users/1.0, and /users/%201%20 as user 1. This is inherited from the existing GET /:id handler and is unchanged here, so it isn't a defect introduced by this PR. Worth fixing in both handlers together, or neither.
  • Malformed-JSON responses — a bad JSON body yields body-parser's default HTML error page instead of the { error } JSON shape used everywhere else. server.js has no error-handling middleware; this affects every route equally, not just PUT.
  • POST / validation — has the same truthy-check weakness and could adopt isNonEmptyString (the helper is at module scope, ready for it), but changing POST is outside this task's scope.
  • Trimming on write — trimming decides validity, but the raw string is what gets stored, so " Grace " is accepted and saved with its spaces. Normalizing stored values would change store behavior beyond the validation fix.

How to test

npm install
npm test        # 9 passing, 0 failing
npm run lint    # clean

tests/update-user.test.js covers the three specified cases: a successful update returns 200 with the new values, an unknown id returns 404, and a missing field returns 400.

To exercise it by hand:

npm run dev

# 200 — update an existing user
curl -X PUT localhost:3000/users/1 \
  -H 'Content-Type: application/json' \
  -d '{"name":"Ada King","email":"ada.king@example.com"}'

# 404 — no such user
curl -i -X PUT localhost:3000/users/9999 \
  -H 'Content-Type: application/json' \
  -d '{"name":"Nobody","email":"nobody@example.com"}'

# 400 — missing field
curl -i -X PUT localhost:3000/users/1 \
  -H 'Content-Type: application/json' \
  -d '{"name":"Only a name"}'

# 400 — non-string values (the case c49994e closes)
curl -i -X PUT localhost:3000/users/1 \
  -H 'Content-Type: application/json' \
  -d '{"name":[],"email":123}'

CI note

GitHub Actions doesn't run on the fork this branch came from — Actions are disabled by default on forks, so ci.yml was never registered there (zero runs on any branch). Both CI steps were run locally against the pushed tree instead: npm run lint exits clean and npm test reports 9/9 passing. CI should execute normally against this PR in the upstream repo.

🤖 Generated with Claude Code

https://claude.ai/code/session_014SJ3RoQf3zafWoKHJu3vqh

MariannaWay and others added 3 commits August 11, 2026 19:54
- Add updateUser(id, { name, email }) to db/store.js, following
  the existing getUserById/createUser pattern
- Add PUT /:id route in routes/users.js: validates required
  fields (400), returns 404 for missing users, 200 with the
  updated user otherwise

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014SJ3RoQf3zafWoKHJu3vqh
- Add isNonEmptyString helper: rejects arrays, numbers, booleans,
  and whitespace-only strings
- Closes the gap found in review where {} or 123 as name/email
  would be accepted and persisted

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014SJ3RoQf3zafWoKHJu3vqh
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014SJ3RoQf3zafWoKHJu3vqh
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant